﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using NLog;
using QuantBox.XApi;
#if EnableUI
using System.Drawing.Design;
using QuantBox.Design;
#endif

namespace QuantBox
{
    using SmartQuant;
    public class QBHistoricalData : Provider, IHistoricalDataProvider, IInstrumentProvider
    {
        private const string MsgInvalidDataRequest = "无效的历史数据请求.";
        private const string MsgProviderNotConnected = "Provider is not connected.";
        private const string MsgUserCancelled = "被用户取消.";
        private const string MsgInvalidDataCenterHost = "无效的数据中心地址.";
        private const string MsgPreQueryNotCompleted = "上一个查询还没有完成.";

        private readonly EventProcessor _processor;
        private readonly Logger _logger;

        private readonly HDataSettings _settings;
        private DataRequestManager _manager;
        private int _instrumentQueryCount;
        internal DataCenterInfo dataCenterInfo;

        private static bool CheckDataRequest(HistoricalDataRequest request)
        {
            if (request.DataType == EventType.Bar)
            {
                if (request.BarType == null)
                {
                    request.BarType = BarType.Time;
                }
                if (request.BarSize == null)
                {
                    request.BarSize = 60;
                }
                if (request.BarType == BarType.Time && request.BarSize % 60 == 0)
                {
                    return true;
                }
            }
            if (request.DataType == EventType.Trade
                || request.DataType == EventType.Ask
                || request.DataType == EventType.Bid
                || request.DataType == EventType.Quote
                || request.DataType == EventType.Tick)
            {
                return true;
            }
            return false;
        }

        private static T CreateInternalInstance<T>()
        {
            var type = typeof(T);

            var list = type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            ConstructorInfo noParams = null;
            foreach (var constructorInfo in list)
            {
                var args = constructorInfo.GetParameters();
                if (0 == args.Length)
                {
                    noParams = constructorInfo;
                    break;
                }
            }
            if (null == noParams)
            {
                throw new NotSupportedException("No constructor without 0 parameter");
            }
            var instance = (T)noParams.Invoke(null);
            return instance;
        }

        private static string GetMacAddress()
        {
            const int minMacAddressLength = 12;
            var macAddress = string.Empty;
            long? maxSpeed = -1;

            foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
            {
                var tempMac = nic.GetPhysicalAddress().ToString();
                if (nic.Speed > maxSpeed
                    && !string.IsNullOrEmpty(tempMac)
                    && tempMac.Length >= minMacAddressLength)
                {
                    maxSpeed = nic.Speed;
                    macAddress = tempMac;
                }
            }

            return macAddress;
        }

        private static string GetClientId()
        {
            try
            {
                return CreateInternalInstance<LicenseManager>().GetHardwareID();
            }
            catch (Exception)
            {
                return GetMacAddress();
            }
        }

        private bool CheckDataCenterInfo()
        {
            var info = GetDataCenterInfo();
            try
            {
                var uri = new Uri(info.Host);
                return uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps;
            }
            catch (ArgumentNullException)
            {
                return false;
            }
            catch (UriFormatException)
            {
                return false;
            }
        }

        private void SendInstrumentDefinition(InstrumentDefinitionRequest request, IEnumerable<Instrument> instruments)
        {
            var list = HDataHelper.FilterInstrument(request, instruments);
            var data = new InstrumentDefinition();
            data.Instruments = list;
            data.ProviderId = Id;
            data.RequestId = request.Id;
            data.TotalNum = 0;
            EmitInstrumentDefinition(data);
        }

        static QBHistoricalData()
        {
            QBHelper.InitNLog();
        }

        public QBHistoricalData(Framework framework)
            : base(framework)
        {
            _processor = new EventProcessor(this);
            _settings = HDataSettings.Load();
            _logger = LogManager.GetLogger(_settings.LogPrefix);
            id = _settings.Id;
            name = _settings.Name;
            description = _settings.Description;
            url = _settings.Url;
            UpdateInfo();
            _logger.Info($"{id}_{name} Initialized.");
        }

        private void UpdateInfo()
        {
            var info = GetDataCenterInfo();
            info.ClientId = GetClientId();
            _settings.Reload();
            info.Host = _settings.DataCenterHost;
            info.TickFileHost = _settings.TickFileHost;
        }

        private void OnProviderError(int errorId, string errorMsg)
        {
            _logger.Warn("id: {0}, msg: {1}", errorId, errorMsg);
            EmitError(errorId, errorId, errorMsg);
        }

        private DataCenterInfo GetDataCenterInfo()
        {
            return dataCenterInfo ?? (dataCenterInfo = new DataCenterInfo());
        }

        internal void RequestFailed(HistoricalDataRequest request, string errorMsg)
        {
            EmitHistoricalDataEnd(request.RequestId, RequestResult.Error, errorMsg);
        }

        internal void RequestFailed(InstrumentDefinitionRequest request, string errorMsg)
        {
            Interlocked.CompareExchange(ref _instrumentQueryCount, 0, 1);
            EmitInstrumentDefinitionEnd(request.Id, RequestResult.Error, errorMsg);
        }

        internal void RequestDataReady(HistoricalDataRequest request, DataObject[] objects)
        {
            var data = new HistoricalData();
            data.RequestId = request.RequestId;
            data.Objects = objects;
            data.DateTime = DateTime.Now;
            data.TotalNum = 0;
            EmitHistoricalData(data);
        }

        internal void RequestDataReady(InstrumentDefinitionRequest request, DataRequestInfo info, Instrument[] insts)
        {
            SendInstrumentDefinition(request, insts.ToList());
        }

        internal void RequestCancelled(InstrumentDefinitionRequest request)
        {
            EmitInstrumentDefinitionEnd(request.Id, RequestResult.Cancelled, MsgUserCancelled);
            Interlocked.CompareExchange(ref _instrumentQueryCount, 0, 1);
        }

        internal void RequestCancelled(HistoricalDataRequest request)
        {
            EmitHistoricalDataEnd(request.RequestId, RequestResult.Cancelled, MsgUserCancelled);
        }

        internal void RequestCompleted(InstrumentDefinitionRequest request)
        {
            EmitInstrumentDefinitionEnd(request.Id, RequestResult.Completed, string.Empty);
            Interlocked.CompareExchange(ref _instrumentQueryCount, 0, 1);
        }

        internal void RequestCompleted(HistoricalDataRequest request)
        {
            EmitHistoricalDataEnd(request.RequestId, RequestResult.Completed, string.Empty);
        }

        protected override void OnConnect()
        {
            if (!CheckDataCenterInfo())
            {
                OnProviderError(-1, MsgInvalidDataCenterHost);
                return;
            }
            UpdateInfo();
            _manager = new DataRequestManager(dataCenterInfo, _logger);
            _manager.DataReady += OnDataReady;
            _manager.RequestCancelled += OnRequestCancelled;
            _manager.RequestCompleted += OnRequestCompleted;
            _manager.RequestFailed += OnRequestFailed;
            base.OnConnect();
        }

        private void OnRequestFailed(object sender, string msg)
        {
            _processor.ProcessFailed((DataRequestInfo)sender, msg);
        }

        private void OnRequestCompleted(DataRequestInfo info)
        {
            _processor.ProcessCompleted(info);
        }

        private void OnRequestCancelled(DataRequestInfo info)
        {
            _processor.ProcessCancelled(info);
        }

        private void OnDataReady(object sender, object[] data)
        {
            _processor.ProcessData((DataRequestInfo)sender, data);
        }

        protected override void OnDisconnect()
        {
            _manager.Close();
            Interlocked.CompareExchange(ref _instrumentQueryCount, 0, 1);
            base.OnDisconnect();
        }

        public override void Cancel(string requestId)
        {
            if (!IsConnected)
            {
                EmitError(MsgProviderNotConnected);
                return;
            }
            _manager.CancelRequest(requestId);
        }

        private string GetInstrumentId(Instrument inst)
        {
            if (inst.AltId.Count(n => n.ProviderId == Id) == 0
                && inst.Exchange == ExchangeNames.CZCE
                && inst.Type == InstrumentType.Future)
            {
                var match = Regex.Match(inst.Symbol, @"([A-Z]+)(\d+)");
                if (match.Success)
                {
                    return $"{match.Groups[1].Value}{inst.Maturity.Year.ToString().Substring(2, 1)}{match.Groups[2].Value}";
                }
            }
            switch (inst.Type)
            {
                case InstrumentType.FutureOption:
                case InstrumentType.Option:
                    return inst.GetSymbol(Id);
                default:
                    return inst.Symbol;
            }
        }

        public override void Send(HistoricalDataRequest request)
        {
            if (!IsConnected)
            {
                Connect();
            }
            if (!IsConnected)
            {
                RequestCancelled(request);
                return;
            }
            if (!CheckDataRequest(request))
            {
                EmitHistoricalDataEnd(request.RequestId, RequestResult.Error, MsgInvalidDataRequest);
                return;
            }
            ResetRequestDataRange(request);
            _manager.Post(new DataRequestInfo
            {
                RequestId = request.RequestId,
                Action = DataRequestAction.DataRequest,
                InstrumentId = GetInstrumentId(request.Instrument),
                PriceFormat = request.Instrument.PriceFormat,
                InstrumentType = TypeConvert.GetInstrumentTypeName(request.Instrument.Type),
                Exchange = request.Instrument.Exchange,
                DateTime1 = request.DateTime1,
                DateTime2 = request.DateTime2,
                BarSize = request.BarSize,
                DataCachePath = DataCachePath,
                EnableCache = EnableCache,
                AttachedData = request,
                QueryRealtimeData = EnableIntradayData
            });
        }

        private static void ResetRequestDataRange(HistoricalDataRequest request)
        {
            var date1 = request.DateTime1 < request.DateTime2 ? request.DateTime1 : request.DateTime2;
            var date2 = request.DateTime2 > request.DateTime1 ? request.DateTime2 : request.DateTime1;
            if ((request.Instrument.Type == InstrumentType.Future
                || request.Instrument.Type == InstrumentType.FutureOption
                || request.Instrument.Type == InstrumentType.Option))
            {
                if (date2 > request.Instrument.Maturity)
                    date2 = request.Instrument.Maturity;
            }
            request.DateTime1 = date1;
            request.DateTime2 = date2;
        }

        private DateTime GetDateTime(string value)
        {
            if (value != null && DateTime.TryParseExact(value, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out var date))
            {
                return date;
            }
            return DateTime.MaxValue;
        }

        private void ProcessInstrumentRequest(InstrumentDefinitionRequest request)
        {
            var info = new DataRequestInfo
            {
                DateTime1 = DateTime.MaxValue,
                RequestId = request.Id,
                Action = DataRequestAction.InstrumentRequest,
                AttachedData = request
            };
            info.DateTime1 = GetDateTime(request.FilterExchange);
            if (info.DateTime1 != DateTime.MaxValue)
            {
                request.FilterExchange = string.Empty;
            }
            if (request.FilterSymbol?.ToUpper() == "MAIN")
            {
                info.MainInstrumentOnly = true;
                if (info.DateTime1 == DateTime.MaxValue)
                {
                    info.DateTime1 = DateTime.Today;
                }
                request.FilterSymbol = string.Empty;
            }
            var reqType = TypeConvert.GetInstrumentTypeName(request.FilterType);
            var list = new List<string>();
            if (string.IsNullOrEmpty(reqType))
            {
                list.AddRange(DataCenterConst.AllTypes);
            }
            else
            {
                list.Add(reqType);
            }
            foreach (var type in list)
            {
                info.RequestInstrumentTypes.Add(type);
            }

            if (info.RequestInstrumentTypes.Count > 0)
            {
                _manager.Post(info);
            }
            else
            {
                RequestCompleted(request);
            }
        }

        public override void Send(InstrumentDefinitionRequest request)
        {
            if (!IsConnected)
            {
                Connect();
            }

            if (Interlocked.CompareExchange(ref _instrumentQueryCount, 1, 0) == 0)
            {
                ProcessInstrumentRequest(request);
            }
            else
            {
                EmitInstrumentDefinitionEnd(request.Id, RequestResult.Error, MsgPreQueryNotCompleted);
            }
        }

        #region UI Settings
        private const string CategorySettings = "Settings";
        private const string InfoSettings = "Information";

        [Category(InfoSettings)]
        [Description("版本")]
        [ReadOnly(true)]
        public string Version => HDataHelper.GetVersionString();

        [Category(CategorySettings)]
        [Description("连接地址")]
        [ReadOnly(false)]
        public string DataCenterHost
        {
            set
            {
                GetDataCenterInfo().Host = value;
                _settings.DataCenterHost = value;
                _settings.Save();
            }
            get
            {
                return GetDataCenterInfo().Host;
            }
        }

        [Category(CategorySettings)]
        [Description("行情数据地址")]
        [ReadOnly(false)]
        public string DataFileHost
        {
            set
            {
                GetDataCenterInfo().TickFileHost = value;
                _settings.TickFileHost = value;
                _settings.Save();
            }
            get
            {
                return GetDataCenterInfo().TickFileHost;
            }
        }

        [Category(CategorySettings)]
        [Description("用户名")]
        [ReadOnly(false)]
        public string User
        {
            set => GetDataCenterInfo().User = value;
            get => GetDataCenterInfo().User;
        }

        [Category(CategorySettings)]
        [Description("密码")]
        [ReadOnly(false)]
        [PasswordPropertyText(true)]
        public string Password
        {
            set => GetDataCenterInfo().Password = value;
            get => GetDataCenterInfo().Password;
        }

        [Category(CategorySettings)]
        [Description("是否导入日内数据")]
        [ReadOnly(false)]
        public bool EnableIntradayData { set; get; } = true;

        [Category(CategorySettings)]
        [Description("在导入Trade数据的同时导入Quote数据")]
        [ReadOnly(false)]
        public bool QuoteAndTrade { set; get; } = true;

        [Category(CategorySettings)]
        [Description("期权回测数据设置")]
        [ReadOnly(true)]
#if !DisableUI
        [Editor(typeof(ChainDataTypeEditor), typeof(UITypeEditor))]
#endif
        public string ChainDataSetting { get; set; }

        [Category(CategorySettings)]
        [Description("期权本地数据路径")]
        [ReadOnly(true)]
        public string ChainDataPath
        {
            set
            {
                _settings.ChainDataPath = value;
                _settings.Save();
            }
            get => _settings.ChainDataPath;
        }

        [Category(CategorySettings)]
        [Description("缓存已导入的数据文件")]
        [Browsable(false)]
        public bool EnableCache { private set; get; } = false;

        [Category(CategorySettings)]
#if !DisableUI
        [Editor(typeof(FolderNameEditor2), typeof(UITypeEditor))]
#endif
        [Description("缓存数据文件路径")]
        [Browsable(false)]
        public string DataCachePath { set; get; } = GetTempDir();

        private static string GetTempDir()
        {
            var path = Path.Combine(Installation.ApplicationDataDir.FullName, "temp");
            try
            {
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
            }
            catch
            {
                // ignored
            }
            return path;
        }
        #endregion
    }
}